/* $Date: 2009-10-14 13:22:33 -0300 (Wed, 14 Oct 2009) $ */
/* $Revision: 1068 $ */

#include "simple.h"
#include <malloc.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include <assert.h>
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <sys/times.h>
#include <stdbool.h>
#include "inode.h"


/*
 * FS simple en memoria
 */

#define INVREP(self) (self!=NULL && (self->hash)!=NULL)

struct simple_s
{
	GHashTable *hash;
};

/*
 * Funciones para usar g_hash_table_new_full.
 * Key = path = string
 * Value = inodo
 */

static void destroy_key(char *key);
static void destroy_value(inode *value);

/*
 * Constructores y Destructores
 */

simple *simple_create(void)
{
	simple *result = NULL;
	result = calloc(1, sizeof(*result));
	if(result != NULL) 
	{
		result->hash = g_hash_table_new_full(g_str_hash, g_str_equal,
		(GDestroyNotify)destroy_key, (GDestroyNotify)destroy_value);
		assert(INVREP(result));
	}
	return result;
}

void simple_destroy(simple *self)
{
	assert(INVREP(self));
	g_hash_table_destroy(self->hash);
	self->hash = NULL;
	free(self);
	self = NULL;
}

static void destroy_key(char *key)
{
	if (key!=NULL)
		free(key);

	key=NULL;
}

static void destroy_value(inode *value)
{
	if (value!=NULL)
		inode_destroy(value);
}


/*
 * Operaciones Básicas
 */

int simple_getattr(simple *self, const char *path, struct stat *stbuf)
{
	inode *i = NULL;
	time_t new_time = 0;

	assert(INVREP(self));
	assert (path!=NULL && stbuf!=NULL);

	if (strcmp(path, "/")==0)
		stbuf->st_mode = S_IFDIR;
	else
	{
		i = g_hash_table_lookup(self->hash, path);
		if (i!=NULL)
		{
			new_time = time(NULL);
			i->metadata.st_atime = new_time;
			i->metadata.st_mtime = new_time;
			i->metadata.st_ctime = new_time;
			*stbuf = i->metadata;
		}
		else
			return ENOENT;
	}

	return 0;
}

int simple_mknod(simple *self, const char *path, mode_t mode)
{
	char *new_key = NULL;
	inode *new_node = NULL;
	int size = 0;
	time_t new_time = 0;

	assert(INVREP(self));
	assert(path!=NULL);

	new_node = g_hash_table_lookup(self->hash, path);
	if (new_node == NULL)
	{
		size = strlen(path)+1;
		new_key = (char *) calloc (size, sizeof(char));
		new_node = inode_create();
		assert(new_key!=NULL && new_node!=NULL);
		strncpy(new_key, path, size);
		
		new_time = time(NULL);
		new_node->metadata.st_mode = mode;
		new_node->metadata.st_uid = getuid();
		new_node->metadata.st_gid = getgid();
		new_node->metadata.st_atime = new_time;
		new_node->metadata.st_mtime = new_time;
		new_node->metadata.st_ctime = new_time;
	
		if (S_ISDIR(mode)) /*es un directorio => data = NULL*/
			new_node->data = NULL;

		g_hash_table_insert(self->hash, new_key, new_node);
	}
	else
		return EEXIST;

	return 0;
}

int simple_unlink(simple *self, const char *path)
{
	inode *i = NULL;

	assert(INVREP(self));
	assert(path!=NULL);
	
	i = g_hash_table_lookup(self->hash, path);

	if(i != NULL)
		g_hash_table_remove(self->hash, path);
	else
		return ENOENT;

	return 0;
}

int simple_rename(simple *self, const char *from, const char *to)
{
	gpointer new_key = NULL, new_value = NULL;
	char *aux = NULL;
	bool esta = false;

	assert(INVREP(self));
	assert(from!=NULL && to!=NULL);	

	esta = g_hash_table_lookup_extended(self->hash, from, &new_key,&new_value);
	if (esta)
	{
		esta = g_hash_table_steal(self->hash, from);
		aux = calloc (strlen(to)+1, sizeof (char));
		assert (new_key != NULL);
		strncpy (aux,to,strlen(to)+1);
		g_hash_table_insert(self->hash, aux, (inode *)new_value);
	}
	else
		return ENOENT;
	
	return 0;
}

int simple_chmod(simple *self, const char *path, mode_t mode)
{
	inode *i = NULL;
	time_t new_time = 0;

	assert(INVREP(self));
	assert(path != NULL);

	i = g_hash_table_lookup(self->hash, path);
	if (i!=NULL)
	{
		new_time = time(NULL);
		i->metadata.st_mode = mode;
		i->metadata.st_ctime = new_time;
		i->metadata.st_mtime = new_time;
	}
	else
		return ENOENT;

	return 0;
}

int simple_chown(simple *self,const char *path, uid_t uid, gid_t gid)
{
	inode *i = NULL;
	time_t new_time = 0;

	assert(INVREP(self));
	assert(path!=NULL);

	i = g_hash_table_lookup(self->hash, path);
	if (i!= NULL)
	{
		new_time = time(NULL);
		i->metadata.st_uid = uid;
		i->metadata.st_gid = gid;
		i->metadata.st_ctime = new_time;
		i->metadata.st_mtime = new_time;
	}
	else
		return ENOENT;

	return 0;
}

int simple_truncate(simple *self, const char *path, off_t size)
{
	inode *i = NULL;
	time_t new_time = 0;

	assert(INVREP(self));
	assert(path!=NULL);

	if (size!=0)
	{
		i = g_hash_table_lookup(self->hash, path);
		assert(i!=NULL);

			if (size != i->metadata.st_size)
			{
				i->data = (char *) realloc (i->data, size);
				new_time = time(NULL);
				i->metadata.st_ctime = new_time;
				i->metadata.st_mtime = new_time;
			}
		
	}

	return 0;
}

int simple_read(simple *self, const char *path,char *buffer, size_t size,
				off_t offset)
{
	inode *i = NULL;

	assert(INVREP(self));
	assert(path!=NULL && buffer!=NULL);

	i = g_hash_table_lookup(self->hash, path);
	assert(i!=NULL);
	
		if (offset < i->metadata.st_size)
		{
			if (size+offset > i->metadata.st_size)
				size = (i->metadata.st_size)-offset;
			memcpy(buffer, &i->data[offset], size);
		/* arriba no usamos strncpy pues necesitamos arrancar desde
		offset */
			i->metadata.st_atime = time(NULL);
		}
		else
			size = 0;

	return size;
}

int simple_write(simple *self, const char *path, const char *buffer, 
				size_t size, off_t offset)
{
	inode *i = NULL;
	time_t new_time = 0;

	assert(INVREP(self));
	assert(path!=NULL && buffer!=NULL);

	i = g_hash_table_lookup(self->hash, path);
	if (i!=NULL)
	{
		if (i->metadata.st_size < (size+offset))
		{
			i->data = (char *) realloc(i->data, (size_t) (size+offset));
			i->metadata.st_size = size+offset;
		}
		new_time = time(NULL);
		memcpy(i->data + offset, buffer, size);
		i->metadata.st_atime = new_time;
		i->metadata.st_ctime = new_time;
		i->metadata.st_mtime = new_time;
	}
	else
		return ENOENT;

	return size;
}

int simple_utimens(simple *self, const char *path, const struct timespec tv[2])
{
	inode *i = NULL;
	
	assert(INVREP(self));
	assert(path!=NULL);

	i = g_hash_table_lookup(self->hash, path);
	if (i!=NULL)
	{
		i->metadata.st_atime = tv[0].tv_sec;
		i->metadata.st_mtime = tv[1].tv_sec;
	}
	else
		return ENOENT;
		
	return 0;
}

/*
 * Operaciones sobre Directorios
 */
int simple_mkdir(simple *self, const char *path, mode_t mode)
{
	inode *i = NULL;

	assert(INVREP(self));
	assert(path!=NULL);
	
	i = g_hash_table_lookup(self->hash, path);
	if (i!=NULL)
		return EEXIST;
	else
		simple_mknod(self, path, mode|S_IFDIR);

	return 0;
}

int simple_rmdir(simple *self, const char *path)
{
	int i = 0, count = 0, path_len = 0, aux_len = 0;
	GList *list;
	char *aux;
	
	assert(INVREP(self));
	assert(path!=NULL);

	list = g_hash_table_get_keys (self->hash);
	path_len = strlen(path);	

	for (i = 0; i < g_list_length(list); i++)
	{
		aux = g_list_nth_data (list, i);
		aux_len = strlen(aux);
		if((strncmp(path, aux, path_len)==0) &&  (aux_len==path_len))
			count++;
	}
	if (count == 1)
		g_hash_table_remove(self->hash, path);
	else
		return -ENOTEMPTY;

	return 0;
}


int simple_readdir(simple *self, const char *path, void *buf, 
				   fuse_fill_dir_t filler)
{

	/* acá está bastante bien explicado el funcionamiento de readdir:
	http://sourceforge.net/apps/mediawiki/fuse/index.php?title=Hello_World*/

	int i = 0;
	char *path_aux = NULL, *key = NULL, *aux = NULL;
	char path_char = '/';
	GList *list = NULL;

	assert(INVREP(self));
	assert(path!=NULL);
	assert(buf!=NULL);
	
	list = g_hash_table_get_keys (self->hash);
	/* todas las keys de la hash en la lista */

	if (g_list_length(list) != 0)
	{
		for (i= 0; i < g_list_length(list); i++)
		{
			key = g_list_nth_data (list,i);
			if (strcmp (path, "/") == 0) /* estoy en el directorio raíz */
			{
				aux = strchr (key+1, '/');
		    	if (aux == NULL)
				/* es un archivo en root o directorio allí mismo */
					filler(buf, key+1, NULL, 0);
			}
			else
			{
		    	path_aux = (char *) calloc (strlen(path)+5, sizeof (char));
				assert (path_aux != NULL);
				path_aux = strcpy (path_aux, path);
				path_aux = strncat (path_aux, &path_char, 1);
				/** path_aux = "path/" **/ 
		    	if (strlen (path_aux) <= strlen (key))
				{
					if(strncmp (path_aux, key, strlen (path_aux)) == 0)
					{
			    		aux = strchr (key+strlen(path_aux)+1, '/');
			    		if (aux == NULL)  
							filler(buf,key+strlen(path_aux), NULL,0);
					}
		    	}
			}
		}
	}
	
	return 0;
}
